Clover icon

compiler

  1. Project Clover database Mon Jan 2 2023 15:09:37 MST
  2. Package com.google.javascript.jscomp

File Normalize.java

 

Coverage histogram

../../../../img/srcFileCovDistChart10.png
0% of files have more coverage

Code metrics

120
272
35
8
847
529
125
0.46
7.77
4.38
3.57

Classes

Class Line # Actions
Normalize 65 30 10 3
0.931818293.2%
Normalize.FindExposeAnnotations 164 9 7 0
1.0100%
Normalize.RewriteExposedProperties 188 13 6 0
1.0100%
Normalize.PropagateConstantAnnotationsOverVars 218 17 9 3
0.990%
Normalize.VerifyConstants 272 31 13 3
0.9494%
Normalize.NormalizeStatements 356 137 60 8
0.9603960596%
Normalize.DuplicateDeclarationHandler 707 33 16 9
0.836363683.6%
Normalize.ScopeTicklingCallback 822 2 4 0
1.0100%
 

Contributing tests

This file is covered by 5505 tests. .

Source view

1    /*
2    * Copyright 2008 The Closure Compiler Authors.
3    *
4    * Licensed under the Apache License, Version 2.0 (the "License");
5    * you may not use this file except in compliance with the License.
6    * You may obtain a copy of the License at
7    *
8    * http://www.apache.org/licenses/LICENSE-2.0
9    *
10    * Unless required by applicable law or agreed to in writing, software
11    * distributed under the License is distributed on an "AS IS" BASIS,
12    * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13    * See the License for the specific language governing permissions and
14    * limitations under the License.
15    */
16    package com.google.javascript.jscomp;
17   
18    import com.google.common.base.Preconditions;
19    import com.google.common.collect.Lists;
20    import com.google.common.collect.Maps;
21    import com.google.common.collect.Sets;
22    import com.google.javascript.jscomp.AbstractCompiler.LifeCycleStage;
23    import com.google.javascript.jscomp.MakeDeclaredNamesUnique.BoilerplateRenamer;
24    import com.google.javascript.jscomp.NodeTraversal.AbstractPostOrderCallback;
25    import com.google.javascript.jscomp.NodeTraversal.Callback;
26    import com.google.javascript.jscomp.Scope.Var;
27    import com.google.javascript.rhino.IR;
28    import com.google.javascript.rhino.JSDocInfo;
29    import com.google.javascript.rhino.Node;
30    import com.google.javascript.rhino.Token;
31   
32    import java.util.Map;
33    import java.util.Set;
34   
35    /**
36    * The goal with this pass is to simplify the other passes,
37    * by making less complex statements.
38    *
39    * Starting with statements like:
40    * var a = 0, b = foo();
41    *
42    * Which become:
43    * var a = 0;
44    * var b = foo();
45    *
46    * The key here is only to break down things that help the other passes
47    * and can be put back together in a form that is at least as small when
48    * all is said and done.
49    *
50    * This pass currently does the following:
51    * 1) Simplifies the AST by splitting var statements, moving initializers
52    * out of for loops, and converting whiles to fors.
53    * 2) Moves hoisted functions to the top of function scopes.
54    * 3) Rewrites unhoisted named function declarations to be var declarations.
55    * 4) Makes all variable names globally unique (extern or otherwise) so that
56    * no value is ever shadowed (note: "arguments" may require special
57    * handling).
58    * 5) Removes duplicate variable declarations.
59    * 6) Marks constants with the IS_CONSTANT_NAME annotation.
60    * 7) Finds properties marked @expose, and rewrites them in [] notation.
61    *
62    * @author johnlenz@google.com (johnlenz)
63    */
64    // public for ReplaceDebugStringsTest
 
65    class Normalize implements CompilerPass {
66   
67    private final AbstractCompiler compiler;
68    private final boolean assertOnChange;
69    private static final boolean CONVERT_WHILE_TO_FOR = true;
70    static final boolean MAKE_LOCAL_NAMES_UNIQUE = true;
71   
72    public static final DiagnosticType CATCH_BLOCK_VAR_ERROR =
73    DiagnosticType.error(
74    "JSC_CATCH_BLOCK_VAR_ERROR",
75    "The use of scope variable {0} is not allowed within a catch block " +
76    "with a catch exception of the same name.");
77   
78   
 
79  24523 toggle Normalize(AbstractCompiler compiler, boolean assertOnChange) {
80  24523 this.compiler = compiler;
81  24523 this.assertOnChange = assertOnChange;
82   
83    // TODO(nicksantos): assertOnChange should only be true if the tree
84    // is normalized.
85    }
86   
 
87  163 toggle static Node parseAndNormalizeSyntheticCode(
88    AbstractCompiler compiler, String code, String prefix) {
89  163 Node js = compiler.parseSyntheticCode(code);
90  163 NodeTraversal.traverse(compiler, js,
91    new Normalize.NormalizeStatements(compiler, false));
92  163 NodeTraversal.traverse(
93    compiler, js,
94    new MakeDeclaredNamesUnique(
95    new BoilerplateRenamer(
96    compiler.getUniqueNameIdSupplier(),
97    prefix)));
98  163 return js;
99    }
100   
 
101  420 toggle static Node parseAndNormalizeTestCode(
102    AbstractCompiler compiler, String code, String prefix) {
103  420 Node js = compiler.parseTestCode(code);
104  420 NodeTraversal.traverse(compiler, js,
105    new Normalize.NormalizeStatements(compiler, false));
106  420 NodeTraversal.traverse(
107    compiler, js,
108    new MakeDeclaredNamesUnique());
109  420 return js;
110    }
111   
 
112  84 toggle private void reportCodeChange(String changeDescription) {
113  84 if (assertOnChange) {
114  0 throw new IllegalStateException(
115    "Normalize constraints violated:\n" + changeDescription);
116    }
117  84 compiler.reportCodeChange();
118    }
119   
 
120  24523 toggle @Override
121    public void process(Node externs, Node root) {
122  24523 new NodeTraversal(
123    compiler, new NormalizeStatements(compiler, assertOnChange))
124    .traverseRoots(externs, root);
125  24521 if (MAKE_LOCAL_NAMES_UNIQUE) {
126  24521 MakeDeclaredNamesUnique renamer = new MakeDeclaredNamesUnique();
127  24521 NodeTraversal t = new NodeTraversal(compiler, renamer);
128  24521 t.traverseRoots(externs, root);
129    }
130    // It is important that removeDuplicateDeclarations runs after
131    // MakeDeclaredNamesUnique in order for catch block exception names to be
132    // handled properly. Specifically, catch block exception names are
133    // only valid within the catch block, but our current Scope logic
134    // has no concept of this and includes it in the containing function
135    // (or global scope). MakeDeclaredNamesUnique makes the catch exception
136    // names unique so that removeDuplicateDeclarations() will properly handle
137    // cases where a function scope variable conflict with a exception name:
138    // function f() {
139    // try {throw 0;} catch(e) {e; /* catch scope 'e'*/}
140    // var e = 1; // f scope 'e'
141    // }
142    // otherwise 'var e = 1' would be rewritten as 'e = 1'.
143    // TODO(johnlenz): Introduce a separate scope for catch nodes.
144  24521 removeDuplicateDeclarations(externs, root);
145  24521 new PropagateConstantAnnotationsOverVars(compiler, assertOnChange)
146    .process(externs, root);
147   
148  24521 FindExposeAnnotations findExposeAnnotations = new FindExposeAnnotations();
149  24521 NodeTraversal.traverse(compiler, root, findExposeAnnotations);
150  24521 if (!findExposeAnnotations.exposedProperties.isEmpty()) {
151  9 NodeTraversal.traverse(compiler, root,
152    new RewriteExposedProperties(
153    findExposeAnnotations.exposedProperties));
154    }
155   
156  24521 if (!compiler.getLifeCycleStage().isNormalized()) {
157  13017 compiler.setLifeCycleStage(LifeCycleStage.NORMALIZED);
158    }
159    }
160   
161    /**
162    * Find all the @expose annotations.
163    */
 
164    private static class FindExposeAnnotations extends AbstractPostOrderCallback {
165    private final Set<String> exposedProperties = Sets.newHashSet();
166   
 
167  604547 toggle @Override public void visit(NodeTraversal t, Node n, Node parent) {
168  604547 if (NodeUtil.isExprAssign(n)) {
169  23024 Node assign = n.getFirstChild();
170  23024 Node lhs = assign.getFirstChild();
171  23024 if (lhs.isGetProp() && isMarkedExpose(assign)) {
172  9 exposedProperties.add(lhs.getLastChild().getString());
173    }
174  581523 } else if (n.isStringKey() && isMarkedExpose(n)) {
175  6 exposedProperties.add(n.getString());
176    }
177    }
178   
 
179  20460 toggle private boolean isMarkedExpose(Node n) {
180  20460 JSDocInfo info = n.getJSDocInfo();
181  20460 return info != null && info.isExpose();
182    }
183    }
184   
185    /**
186    * Rewrite all exposed properties in [] form.
187    */
 
188    private class RewriteExposedProperties
189    extends AbstractPostOrderCallback {
190    private final Set<String> exposedProperties;
191   
 
192  9 toggle RewriteExposedProperties(Set<String> exposedProperties) {
193  9 this.exposedProperties = exposedProperties;
194    }
195   
 
196  303 toggle @Override public void visit(NodeTraversal t, Node n, Node parent) {
197  303 if (n.isGetProp()) {
198  30 String propName = n.getLastChild().getString();
199  30 if (exposedProperties.contains(propName)) {
200  21 Node obj = n.removeFirstChild();
201  21 Node prop = n.removeFirstChild();
202  21 n.getParent().replaceChild(n, IR.getelem(obj, prop));
203  21 compiler.reportCodeChange();
204    }
205  273 } else if (n.isStringKey()) {
206  12 String propName = n.getString();
207  12 if (exposedProperties.contains(propName)) {
208  9 n.setQuotedString();
209  9 compiler.reportCodeChange();
210    }
211    }
212    }
213    }
214   
215    /**
216    * Propagate constant annotations over the Var graph.
217    */
 
218    static class PropagateConstantAnnotationsOverVars
219    extends AbstractPostOrderCallback
220    implements CompilerPass {
221    private final AbstractCompiler compiler;
222    private final boolean assertOnChange;
223   
 
224  24521 toggle PropagateConstantAnnotationsOverVars(
225    AbstractCompiler compiler, boolean forbidChanges) {
226  24521 this.compiler = compiler;
227  24521 this.assertOnChange = forbidChanges;
228    }
229   
 
230  24521 toggle @Override
231    public void process(Node externs, Node root) {
232  24521 new NodeTraversal(compiler, this).traverseRoots(externs, root);
233    }
234   
 
235  937049 toggle @Override
236    public void visit(NodeTraversal t, Node n, Node parent) {
237    // Note: Constant properties annotations are not propagated.
238  937049 if (n.isName()) {
239  243255 if (n.getString().isEmpty()) {
240  17606 return;
241    }
242   
243  225649 JSDocInfo info = null;
244    // Find the JSDocInfo for a top-level variable.
245  225649 Var var = t.getScope().getVar(n.getString());
246  225649 if (var != null) {
247  199405 info = var.getJSDocInfo();
248    }
249   
250  225649 boolean shouldBeConstant =
251    (info != null && info.isConstant()) ||
252    NodeUtil.isConstantByConvention(
253    compiler.getCodingConvention(), n, parent);
254  225649 boolean isMarkedConstant = n.getBooleanProp(Node.IS_CONSTANT_NAME);
255  225649 if (shouldBeConstant && !isMarkedConstant) {
256  1947 if (assertOnChange) {
257  0 String name = n.getString();
258  0 throw new IllegalStateException(
259    "Unexpected const change.\n" +
260    " name: "+ name + "\n" +
261    " parent:" + n.getParent().toStringTree());
262    }
263  1947 n.putBooleanProp(Node.IS_CONSTANT_NAME, true);
264    }
265    }
266    }
267    }
268   
269    /**
270    * Walk the AST tree and verify that constant names are used consistently.
271    */
 
272    static class VerifyConstants extends AbstractPostOrderCallback
273    implements CompilerPass {
274   
275    final private AbstractCompiler compiler;
276    final private boolean checkUserDeclarations;
277   
 
278  9866 toggle VerifyConstants(AbstractCompiler compiler, boolean checkUserDeclarations) {
279  9866 this.compiler = compiler;
280  9866 this.checkUserDeclarations = checkUserDeclarations;
281    }
282   
 
283  9866 toggle @Override
284    public void process(Node externs, Node root) {
285  9866 Node externsAndJs = root.getParent();
286  9866 Preconditions.checkState(externsAndJs != null);
287  9866 Preconditions.checkState(externsAndJs.hasChild(externs));
288   
289  9866 NodeTraversal.traverseRoots(
290    compiler, Lists.newArrayList(externs, root), this);
291    }
292   
293    private Map<String, Boolean> constantMap = Maps.newHashMap();
294   
 
295  321087 toggle @Override
296    public void visit(NodeTraversal t, Node n, Node parent) {
297  321087 if (n.isName()) {
298  81123 String name = n.getString();
299  81123 if (n.getString().isEmpty()) {
300  5241 return;
301    }
302   
303  75882 boolean isConst = n.getBooleanProp(Node.IS_CONSTANT_NAME);
304  75882 if (checkUserDeclarations) {
305  75882 boolean expectedConst = false;
306  75882 CodingConvention convention = compiler.getCodingConvention();
307  75882 if (NodeUtil.isConstantName(n)
308    || NodeUtil.isConstantByConvention(convention, n, parent)) {
309  1372 expectedConst = true;
310    } else {
311  74510 expectedConst = false;
312   
313  74510 JSDocInfo info = null;
314  74510 Var var = t.getScope().getVar(n.getString());
315  74510 if (var != null) {
316  64458 info = var.getJSDocInfo();
317    }
318   
319  74510 if (info != null && info.isConstant()) {
320  0 expectedConst = true;
321    } else {
322  74510 expectedConst = false;
323    }
324    }
325   
326  75882 if (expectedConst) {
327  1372 Preconditions.checkState(expectedConst == isConst,
328    "The name %s is not annotated as constant.", name);
329    } else {
330  74510 Preconditions.checkState(expectedConst == isConst,
331    "The name %s should not be annotated as constant.", name);
332    }
333    }
334   
335  75882 Boolean value = constantMap.get(name);
336  75882 if (value == null) {
337  46971 constantMap.put(name, isConst);
338    } else {
339  28911 Preconditions.checkState(value.booleanValue() == isConst,
340    "The name %s is not consistently annotated as constant.", name);
341    }
342    }
343    }
344    }
345   
346    /**
347    * Simplify the AST:
348    * - VAR declarations split, so they represent exactly one child
349    * declaration.
350    * - WHILEs are converted to FORs
351    * - FOR loop are initializers are moved out of the FOR structure
352    * - LABEL node of children other than LABEL, BLOCK, WHILE, FOR, or DO are
353    * moved into a block.
354    * - Add constant annotations based on coding convention.
355    */
 
356    static class NormalizeStatements implements Callback {
357    private final AbstractCompiler compiler;
358    private final boolean assertOnChange;
359   
 
360  25163 toggle NormalizeStatements(AbstractCompiler compiler, boolean assertOnChange) {
361  25163 this.compiler = compiler;
362  25163 this.assertOnChange = assertOnChange;
363    }
364   
 
365  5466 toggle private void reportCodeChange(String changeDescription) {
366  5466 if (assertOnChange) {
367  2 throw new IllegalStateException(
368    "Normalize constraints violated:\n" + changeDescription);
369    }
370  5464 compiler.reportCodeChange();
371    }
372   
 
373  1020458 toggle @Override
374    public boolean shouldTraverse(NodeTraversal t, Node n, Node parent) {
375  1020458 doStatementNormalizations(t, n, parent);
376   
377  1020458 return true;
378    }
379   
 
380  1020454 toggle @Override
381    public void visit(NodeTraversal t, Node n, Node parent) {
382  1020454 switch (n.getType()) {
383  192 case Token.WHILE:
384  192 if (CONVERT_WHILE_TO_FOR) {
385  192 Node expr = n.getFirstChild();
386  192 n.setType(Token.FOR);
387  192 Node empty = IR.empty();
388  192 empty.copyInformationFrom(n);
389  192 n.addChildBefore(empty, expr);
390  192 n.addChildAfter(empty.cloneNode(), expr);
391  192 reportCodeChange("WHILE node");
392    }
393  190 break;
394   
395  56059 case Token.FUNCTION:
396  56059 normalizeFunctionDeclaration(n);
397  56059 break;
398   
399  261375 case Token.NAME:
400  93716 case Token.STRING:
401  7494 case Token.STRING_KEY:
402  221 case Token.GETTER_DEF:
403  195 case Token.SETTER_DEF:
404  363001 if (!compiler.getLifeCycleStage().isNormalizedObfuscated()) {
405  358143 annotateConstantsByConvention(n, parent);
406    }
407  363001 break;
408   
409  57 case Token.CAST:
410  57 parent.replaceChild(n, n.removeFirstChild());
411  57 break;
412    }
413    }
414   
415    /**
416    * Mark names and properties that are constants by convention.
417    */
 
418  358143 toggle private void annotateConstantsByConvention(Node n, Node parent) {
419  358143 Preconditions.checkState(
420    n.isName()
421    || n.isString()
422    || n.isStringKey()
423    || n.isGetterDef()
424    || n.isSetterDef());
425   
426    // There are only two cases where a string token
427    // may be a variable reference: The right side of a GETPROP
428    // or an OBJECTLIT key.
429  358143 boolean isObjLitKey = NodeUtil.isObjectLitKey(n, parent);
430  358143 boolean isProperty = isObjLitKey ||
431    (parent.isGetProp() &&
432    parent.getLastChild() == n);
433  358143 if (n.isName() || isProperty) {
434  350304 boolean isMarkedConstant = n.getBooleanProp(Node.IS_CONSTANT_NAME);
435  350304 if (!isMarkedConstant &&
436    NodeUtil.isConstantByConvention(
437    compiler.getCodingConvention(), n, parent)) {
438  2511 if (assertOnChange) {
439  0 String name = n.getString();
440  0 throw new IllegalStateException(
441    "Unexpected const change.\n" +
442    " name: "+ name + "\n" +
443    " parent:" + n.getParent().toStringTree());
444    }
445  2511 n.putBooleanProp(Node.IS_CONSTANT_NAME, true);
446    }
447    }
448    }
449   
450    /**
451    * Rewrite named unhoisted functions declarations to a known
452    * consistent behavior so we don't to different logic paths for the same
453    * code. From:
454    * function f() {}
455    * to:
456    * var f = function () {};
457    */
 
458  56059 toggle private void normalizeFunctionDeclaration(Node n) {
459  56059 Preconditions.checkState(n.isFunction());
460  56059 if (!NodeUtil.isFunctionExpression(n)
461    && !NodeUtil.isHoistedFunctionDeclaration(n)) {
462  65 rewriteFunctionDeclaration(n);
463    }
464    }
465   
466    /**
467    * Rewrite the function declaration from:
468    * function x() {}
469    * FUNCTION
470    * NAME
471    * LP
472    * BLOCK
473    * to:
474    * var x = function() {};
475    * VAR
476    * NAME
477    * FUNCTION
478    * NAME (w/ empty string)
479    * LP
480    * BLOCK
481    */
 
482  65 toggle private void rewriteFunctionDeclaration(Node n) {
483    // Prepare a spot for the function.
484  65 Node oldNameNode = n.getFirstChild();
485  65 Node fnNameNode = oldNameNode.cloneNode();
486  65 Node var = IR.var(fnNameNode).srcref(n);
487   
488    // Prepare the function
489  65 oldNameNode.setString("");
490   
491    // Move the function
492  65 Node parent = n.getParent();
493  65 parent.replaceChild(n, var);
494  65 fnNameNode.addChildToFront(n);
495   
496  65 reportCodeChange("Function declaration");
497    }
498   
499    /**
500    * Do normalizations that introduce new siblings or parents.
501    */
 
502  1020458 toggle private void doStatementNormalizations(
503    NodeTraversal t, Node n, Node parent) {
504  1020458 if (n.isLabel()) {
505  378 normalizeLabels(n);
506    }
507   
508    // Only inspect the children of SCRIPTs, BLOCKs and LABELs, as all these
509    // are the only legal place for VARs and FOR statements.
510  1020458 if (NodeUtil.isStatementBlock(n) || n.isLabel()) {
511  165068 extractForInitializer(n, null, null);
512    }
513   
514    // Only inspect the children of SCRIPTs, BLOCKs, as all these
515    // are the only legal place for VARs.
516  1020458 if (NodeUtil.isStatementBlock(n)) {
517  164690 splitVarDeclarations(n);
518    }
519   
520  1020458 if (n.isFunction()) {
521  56059 moveNamedFunctions(n.getLastChild());
522    }
523    }
524   
525    // TODO(johnlenz): Move this to NodeTypeNormalizer once the unit tests are
526    // fixed.
527    /**
528    * Limit the number of special cases where LABELs need to be handled. Only
529    * BLOCK and loops are allowed to be labeled. Loop labels must remain in
530    * place as the named continues are not allowed for labeled blocks.
531    */
 
532  378 toggle private void normalizeLabels(Node n) {
533  378 Preconditions.checkArgument(n.isLabel());
534   
535  378 Node last = n.getLastChild();
536  378 switch (last.getType()) {
537  48 case Token.LABEL:
538  170 case Token.BLOCK:
539  33 case Token.FOR:
540  0 case Token.WHILE:
541  0 case Token.DO:
542  251 return;
543  127 default:
544  127 Node block = IR.block();
545  127 block.copyInformationFrom(last);
546  127 n.replaceChild(last, block);
547  127 block.addChildToFront(last);
548  127 reportCodeChange("LABEL normalization");
549  127 return;
550    }
551    }
552   
553    /**
554    * Bring the initializers out of FOR loops. These need to be placed
555    * before any associated LABEL nodes. This needs to be done from the top
556    * level label first so this is called as a pre-order callback (from
557    * shouldTraverse).
558    *
559    * @param n The node to inspect.
560    * @param before The node to insert the initializer before.
561    * @param beforeParent The parent of the node before which the initializer
562    * will be inserted.
563    */
 
564  165506 toggle private void extractForInitializer(
565    Node n, Node before, Node beforeParent) {
566   
567  401508 for (Node next, c = n.getFirstChild(); c != null; c = next) {
568  236002 next = c.getNext();
569  236002 Node insertBefore = (before == null) ? c : before;
570  236002 Node insertBeforeParent = (before == null) ? n : beforeParent;
571  236002 switch (c.getType()) {
572  438 case Token.LABEL:
573  438 extractForInitializer(c, insertBefore, insertBeforeParent);
574  438 break;
575  1540 case Token.FOR:
576  1540 if (NodeUtil.isForIn(c)) {
577  469 Node first = c.getFirstChild();
578  469 if (first.isVar()) {
579    // Transform:
580    // for (var a = 1 in b) {}
581    // to:
582    // var a = 1; for (a in b) {};
583  191 Node newStatement = first;
584    // Clone just the node, to remove any initialization.
585  191 Node name = newStatement.getFirstChild().cloneNode();
586  191 first.getParent().replaceChild(first, name);
587  191 insertBeforeParent.addChildBefore(newStatement, insertBefore);
588  191 reportCodeChange("FOR-IN var declaration");
589    }
590  1071 } else if (!c.getFirstChild().isEmpty()) {
591  463 Node init = c.getFirstChild();
592  463 Node empty = IR.empty();
593  463 empty.copyInformationFrom(c);
594  463 c.replaceChild(init, empty);
595   
596  463 Node newStatement;
597    // Only VAR statements, and expressions are allowed,
598    // but are handled differently.
599  463 if (init.isVar()) {
600  391 newStatement = init;
601    } else {
602  72 newStatement = NodeUtil.newExpr(init);
603    }
604   
605  463 insertBeforeParent.addChildBefore(newStatement, insertBefore);
606  463 reportCodeChange("FOR initializer");
607    }
608  1540 break;
609    }
610    }
611    }
612   
613    /**
614    * Split a var node such as:
615    * var a, b;
616    * into individual statements:
617    * var a;
618    * var b;
619    * @param n The whose children we should inspect.
620    */
 
621  164690 toggle private void splitVarDeclarations(Node n) {
622  399714 for (Node next, c = n.getFirstChild(); c != null; c = next) {
623  235024 next = c.getNext();
624  235024 if (c.isVar()) {
625  64378 if (assertOnChange && !c.hasChildren()) {
626  0 throw new IllegalStateException("Empty VAR node.");
627    }
628   
629  68688 while (c.getFirstChild() != c.getLastChild()) {
630  4310 Node name = c.getFirstChild();
631  4310 c.removeChild(name);
632  4310 Node newVar = IR.var(name).srcref(n);
633  4310 n.addChildBefore(newVar, c);
634  4310 reportCodeChange("VAR with multiple children");
635    }
636    }
637    }
638    }
639   
640    /**
641    * Move all the functions that are valid at the execution of the first
642    * statement of the function to the beginning of the function definition.
643    */
 
644  56059 toggle private void moveNamedFunctions(Node functionBody) {
645  56059 Preconditions.checkState(
646    functionBody.getParent().isFunction());
647  56059 Node previous = null;
648  56059 Node current = functionBody.getFirstChild();
649    // Skip any declarations at the beginning of the function body, they
650    // are already in the right place.
651  56437 while (current != null && NodeUtil.isFunctionDeclaration(current)) {
652  378 previous = current;
653  378 current = current.getNext();
654    }
655   
656    // Find any remaining declarations and move them.
657  56059 Node insertAfter = previous;
658  85489 while (current != null) {
659    // Save off the next node as the current node maybe removed.
660  29430 Node next = current.getNext();
661  29430 if (NodeUtil.isFunctionDeclaration(current)) {
662    // Remove the declaration from the body.
663  118 Preconditions.checkNotNull(previous);
664  118 functionBody.removeChildAfter(previous);
665   
666    // Read the function at the top of the function body (after any
667    // previous declarations).
668  118 insertAfter = addToFront(functionBody, current, insertAfter);
669  118 reportCodeChange("Move function declaration not at top of function");
670    } else {
671    // Update the previous only if the current node hasn't been moved.
672  29312 previous = current;
673    }
674  29430 current = next;
675    }
676    }
677   
678    /**
679    * @param after The child node to insert the newChild after, or null if
680    * newChild should be added to the front of parent's child list.
681    * @return The inserted child node.
682    */
 
683  118 toggle private Node addToFront(Node parent, Node newChild, Node after) {
684  118 if (after == null) {
685  100 parent.addChildToFront(newChild);
686    } else {
687  18 parent.addChildAfter(newChild, after);
688    }
689  118 return newChild;
690    }
691    }
692   
693    /**
694    * Remove duplicate VAR declarations.
695    */
 
696  24521 toggle private void removeDuplicateDeclarations(Node externs, Node root) {
697  24521 Callback tickler = new ScopeTicklingCallback();
698  24521 ScopeCreator scopeCreator = new SyntacticScopeCreator(
699    compiler, new DuplicateDeclarationHandler());
700  24521 NodeTraversal t = new NodeTraversal(compiler, tickler, scopeCreator);
701  24521 t.traverseRoots(externs, root);
702    }
703   
704    /**
705    * ScopeCreator duplicate declaration handler.
706    */
 
707    private final class DuplicateDeclarationHandler implements
708    SyntacticScopeCreator.RedeclarationHandler {
709   
710    private Set<Var> hasOkDuplicateDeclaration = Sets.newHashSet();
711   
712    /**
713    * Remove duplicate VAR declarations encountered discovered during
714    * scope creation.
715    */
 
716  162 toggle @Override
717    public void onRedeclaration(
718    Scope s, String name, Node n, CompilerInput input) {
719  162 Preconditions.checkState(n.isName());
720  162 Node parent = n.getParent();
721  162 Var v = s.getVar(name);
722   
723  162 if (v != null && s.isGlobal()) {
724    // We allow variables to be duplicate declared if one
725    // declaration appears in source and the other in externs.
726    // This deals with issues where a browser built-in is declared
727    // in one browser but not in another.
728  126 if (v.isExtern() && !input.isExtern()) {
729  66 if (hasOkDuplicateDeclaration.add(v)) {
730  63 return;
731    }
732    }
733    }
734   
735    // If name is "arguments", Var maybe null.
736  99 if (v != null && v.getParentNode().isCatch()) {
737    // Redeclaration of a catch expression variable is hard to model
738    // without support for "with" expressions.
739    // The ECMAScript spec (section 12.14), declares that a catch
740    // "catch (e) {}" is handled like "with ({'e': e}) {}" so that
741    // "var e" would refer to the scope variable, but any following
742    // reference would still refer to "e" of the catch expression.
743    // Until we have support for this disallow it.
744    // Currently the Scope object adds the catch expression to the
745    // function scope, which is technically not true but a good
746    // approximation for most uses.
747   
748    // TODO(johnlenz): Consider improving how scope handles catch
749    // expression.
750   
751    // Use the name of the var before it was made unique.
752  12 name = MakeDeclaredNamesUnique.ContextualRenameInverter.getOrginalName(
753    name);
754  12 compiler.report(
755    JSError.make(
756    input.getName(), n,
757    CATCH_BLOCK_VAR_ERROR, name));
758  87 } else if (v != null && parent.isFunction()) {
759  9 if (v.getParentNode().isVar()) {
760  6 s.undeclare(v);
761  6 s.declare(name, n, n.getJSType(), v.input);
762  6 replaceVarWithAssignment(v.getNameNode(), v.getParentNode(),
763    v.getParentNode().getParent());
764    }
765  78 } else if (parent.isVar()) {
766  78 Preconditions.checkState(parent.hasOneChild());
767   
768  78 replaceVarWithAssignment(n, parent, parent.getParent());
769    }
770    }
771   
772    /**
773    * Remove the parent VAR. There are three cases that need to be handled:
774    * 1) "var a = b;" which is replaced with "a = b"
775    * 2) "label:var a;" which is replaced with "label:;". Ideally, the
776    * label itself would be removed but that is not possible in the
777    * context in which "onRedeclaration" is called.
778    * 3) "for (var a in b) ..." which is replaced with "for (a in b)..."
779    * Cases we don't need to handle are VARs with multiple children,
780    * which have already been split into separate declarations, so there
781    * is no need to handle that here, and "for (var a;;);", which has
782    * been moved out of the loop.
783    * The result of this is that in each case the parent node is replaced
784    * which is generally dangerous in a traversal but is fine here with
785    * the scope creator, as the next node of interest is the parent's
786    * next sibling.
787    */
 
788  84 toggle private void replaceVarWithAssignment(Node n, Node parent, Node gramps) {
789  84 if (n.hasChildren()) {
790    // The * is being initialize, preserve the new value.
791  36 parent.removeChild(n);
792    // Convert "var name = value" to "name = value"
793  36 Node value = n.getFirstChild();
794  36 n.removeChild(value);
795  36 Node replacement = IR.assign(n, value);
796  36 replacement.copyInformationFrom(parent);
797  36 gramps.replaceChild(parent, NodeUtil.newExpr(replacement));
798    } else {
799    // It is an empty reference remove it.
800  48 if (NodeUtil.isStatementBlock(gramps)) {
801  48 gramps.removeChild(parent);
802  0 } else if (gramps.isFor()) {
803    // This is the "for (var a in b)..." case. We don't need to worry
804    // about initializers in "for (var a;;)..." as those are moved out
805    // as part of the other normalizations.
806  0 parent.removeChild(n);
807  0 gramps.replaceChild(parent, n);
808    } else {
809  0 Preconditions.checkState(gramps.isLabel());
810    // We should never get here. LABELs with a single VAR statement should
811    // already have been normalized to have a BLOCK.
812  0 throw new IllegalStateException("Unexpected LABEL");
813    }
814    }
815  84 reportCodeChange("Duplicate VAR declaration");
816    }
817    }
818   
819    /**
820    * A simple class that causes scope to be created.
821    */
 
822    private final class ScopeTicklingCallback
823    implements NodeTraversal.ScopedCallback {
 
824  77812 toggle @Override
825    public void enterScope(NodeTraversal t) {
826    // Cause the scope to be created, which will cause duplicate
827    // to be found.
828  77812 t.getScope();
829    }
830   
 
831  77812 toggle @Override
832    public void exitScope(NodeTraversal t) {
833    // Nothing to do.
834    }
835   
 
836  937049 toggle @Override
837    public boolean shouldTraverse(
838    NodeTraversal nodeTraversal, Node n, Node parent) {
839  937049 return true;
840    }
841   
 
842  937049 toggle @Override
843    public void visit(NodeTraversal t, Node n, Node parent) {
844    // Nothing to do.
845    }
846    }
847    }